/**
 * 
 */
package parsetree.util;

import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Arrays.fill;

import java.util.LinkedList;

import metagrammar.Operator;
import parsetree.ElementFactory;
import parsetree.MultipartOperatorSpec;
import parsetree.OperatorSpec;

/**
 * @author Joshua Lockerman
 *
 */
public class OperatorSpecFactory {
    
    public static OperatorSpecFactory forOperator(Operator operator)
    {
        return new OperatorSpecFactory(operator);
    }
    
    private static class Position {
        private final int position;
        
        public Position(int position)
        {
            this.position = position;
        }
        
        public int getPosition()
        {
            return position;
        }
        
        @Override
        public String toString()
        {
            return position + "";
        }
    }
    
    private static class LiteralPositionPair extends Position {
        
        private StringBuilder literal;
        
        public LiteralPositionPair(String literal, int position)
        {
            super(position);
            this.literal = new StringBuilder(literal);
        }
        
        public void setLiteral(String literal)
        {
            this.literal = new StringBuilder(literal);
        }
        
        public String getLiteral()
        {
            return literal.toString();
        }
        
        public void appendToLiteral(String literal)
        {
            this.literal.append(literal);
        }
        
        @Override
        public String toString()
        {
            return "[" + super.toString() + ", " + literal + "]";
        }
    }
    
    private static class InputPositionPair extends Position {
        
        private final int inputPosition;
        
        public InputPositionPair(int inputTuplePosition, int position)
        {
            super(position);
            this.inputPosition = inputTuplePosition;
        }
        
        public int getPositionInInputTuple()
        {
            return inputPosition;
        }
        
        @Override
        public String toString()
        {
            return "[" + super.toString() + ", " + inputPosition + "]";
        }
    }
    
    private final Operator operator;
    private final boolean[] inputUsed;
    
    private final LinkedList<LiteralPositionPair> literalSegments;
    private final LinkedList<InputPositionPair> inputTupleSegments;
    private Position lastPosition;
    
    private OperatorSpecFactory(Operator operator)
    {
        this.operator = operator;
        inputUsed = new boolean[operator.getInputTypes().size()];
        fill(inputUsed, false);
        
        literalSegments = new LinkedList<LiteralPositionPair>();
        inputTupleSegments = new LinkedList<InputPositionPair>();
        lastPosition = null;
    }
    
    public OperatorSpecFactory followedByInput(int inputTupleLocation)
    {
        checkArgument( !inputUsed[inputTupleLocation]);
        if(lastPosition == null) {
            lastPosition = new InputPositionPair(inputTupleLocation, 0);
        }
        else {
            lastPosition = new InputPositionPair(inputTupleLocation, lastPosition.getPosition() + 1);
        }
        inputUsed[inputTupleLocation] = true;
        inputTupleSegments.add((InputPositionPair) lastPosition);
        //TODO
        return this;
    }
    
    public OperatorSpecFactory followedBy(String substring)
    {
        if(substring.equals("")) {
            return this;
        }
        if(lastPosition instanceof LiteralPositionPair) {
            ((LiteralPositionPair) lastPosition).appendToLiteral(substring);
            return this;
        }
        else if(lastPosition == null) {
            lastPosition = new LiteralPositionPair(substring, 0);
        } 
        else {
            lastPosition = new LiteralPositionPair(substring, lastPosition.getPosition() + 1);
        }
        literalSegments.add((LiteralPositionPair) lastPosition);
        //TODO
        return this;
    }
    
    public ElementFactory createSpec()
    {
        for(int i = 0; i < inputUsed.length; i++) {
            if( !inputUsed[i]) {
                inputTupleSegments.add(new InputPositionPair(i, lastPosition.getPosition() + 1));
            }
        }
        int[] correctedOrder = new int[inputUsed.length];
        int i = 0;
        for(InputPositionPair pair : inputTupleSegments) {
            correctedOrder[i] = pair.getPositionInInputTuple();
            i++;
        }
        if(literalSegments.size() == 1) {
            LiteralPositionPair literalPair = literalSegments.getLast();
            String literal = literalPair.getLiteral();
            int beforePosition = literalPair.getPosition();
            return new OperatorSpec(operator, correctedOrder, literal, beforePosition);
        }
        MultipartOperatorSpec.LiteralPositionPair[] literalPairs = new MultipartOperatorSpec.LiteralPositionPair[literalSegments.size()];
        i = 0;
        for(LiteralPositionPair literalPair : literalSegments) {
            literalPairs[i] = new MultipartOperatorSpec.LiteralPositionPair(
                    literalPair.getLiteral(), literalPair.getPosition());
            i++;
        }
        return new MultipartOperatorSpec(operator, correctedOrder, literalPairs);
    }
    
    public boolean isInputUsed(int inputTupleLocation)
    {
        return inputUsed[inputTupleLocation];
    }
    
    public Operator getOperator()
    {
        return operator;
    }
}
